package com.leyou.search.service.impl;

import com.leyou.common.domain.PageResult;
import com.leyou.common.enums.ItemExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.dto.BrandDto;
import com.leyou.dto.CategoryDto;
import com.leyou.dto.SpecParamDTO;
import com.leyou.search.doc.Good;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.feignclient.ItemClient;
import com.leyou.search.service.SearchService;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @version V1.0
 * @author: Ljxpa
 * @date: 2020/03/12 17:00
 * @description: 搜索业务处理
 */
@Service
public class SearchServiceImpl implements SearchService {

	@Autowired
	private ElasticsearchTemplate esTemplate;//该模板类由spring自动生成加入到spring容器中

	@Autowired
	private ItemClient itemClient;

	/**
	 * 处理搜索的业务逻辑，
	 *
	 * @param searchRequest 含搜索的关键字，页数的vo对象
	 * @return 从索引库中搜索到的good的list集合
	 */
	@Override
	public PageResult<Good> pageQueryGoods(SearchRequest searchRequest) {
		//1，校验请求对象是否为空
		checkSearchRequest(searchRequest);
		//2，获取布尔查询对象
		BoolQueryBuilder boolQueryBuilder = getBoolQueryBuilder(searchRequest,"filter");

		//3，构建本地查询对象
		NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

		//4，添加布尔查询条件
		queryBuilder.withQuery(boolQueryBuilder);

		//5，添加分页查询条件,当每页条数的为null时，springDataElasticSearch默认是每页20条记录
		queryBuilder.withPageable(PageRequest.of(searchRequest.getPage()-1,searchRequest.getSize()));

		//6，使用esTemplate对象发起查询并获取查询结果
		AggregatedPage<Good> goods = esTemplate.queryForPage(queryBuilder.build(), Good.class);

		//7，根据查询结果封装前端需要的结果对象
		int totalPages = goods.getTotalPages();
		long totalElements = goods.getTotalElements();
		PageResult<Good> goodPageResult = new PageResult<>();
		goodPageResult.setItems(goods.getContent());
		goodPageResult.setTotal(totalElements);
		goodPageResult.setTotalPage(totalPages);

		//8，返回分页结果对象
		return goodPageResult;
	}

	/**
	 * 根据用户传入的查询参数，构建一个boolQueryBuilder对象并返回
	 * @param searchRequest 前端传入的搜索参数对象
	 * @param booleanParam 在布尔查询中添加查询的类型，例：filter，must等等
	 * @return boolQueryBuilder对象
	 */
	private BoolQueryBuilder getBoolQueryBuilder(SearchRequest searchRequest,String booleanParam) {
		//1，创建一个布尔查询构建对象
		BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

		//2，添加布尔查询条件：根据all字段进行匹配查询，并设置分词连接关系为and
		boolQueryBuilder.must(QueryBuilders.matchQuery("all",searchRequest.getKey()).operator(Operator.AND));//添加布尔查询的条件，

		//3，获取过滤请求
		Map<String, String> filter = searchRequest.getFilter();

		//4，判断：如果过滤选项不为空，则设置过滤条件
		if(!CollectionUtils.isEmpty(filter)){
			for (Map.Entry<String, String> entry : filter.entrySet()) {
				String key = entry.getKey();
				try {
					if(key.equals("品牌")){
						//4.1，如果用户选择通过品牌过滤，则设置过滤条件为根据brandId过滤
						// boolQueryBuilder.filter(QueryBuilders.termQuery("brandId",entry.getValue()));

						boolQueryBuilder.getClass()
								.getDeclaredMethod(booleanParam, QueryBuilder.class)
								.invoke(boolQueryBuilder,QueryBuilders.termQuery("brandId",entry.getValue()));
					}else if(key.equals("分类")){
						//4.2，如果用户选择通过分类过滤，则设置过滤条件为根据categoryId过滤
						// boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId",entry.getValue()));
						boolQueryBuilder.getClass()
								.getDeclaredMethod(booleanParam,QueryBuilder.class)
								.invoke(boolQueryBuilder,QueryBuilders.termQuery("categoryId",entry.getValue()));
					}else{
						//4.3，如果不是品牌和分类，则就是根据参数规格过滤，参数规格需要添加前后缀，然后进行过滤
						// boolQueryBuilder.filter(QueryBuilders.termQuery("specs."+key+".keyword",entry.getValue()));

						boolQueryBuilder.getClass()
								.getDeclaredMethod(booleanParam,QueryBuilder.class)
								.invoke(boolQueryBuilder,QueryBuilders.termQuery("specs." + key + ".keyword", entry.getValue()));
					}
				} catch (Exception e) {
					e.printStackTrace();
					throw new LyException(ItemExceptionEnum.SERVER_OPERATION_ERROR);
				}
			}
		}

		//5，返回布尔查询对象
		return boolQueryBuilder;
	}



	/**
	 * 根据用户的输入内容获取可供选择的分类，品牌的  Map<String,List<?>集合对象
	 * @param searchRequest 用户输入的搜索内容
	 * @return Map<String, List < ?>>
	 */
	@Override
	public Map<String, List<?>> queryFilterListByKey(SearchRequest searchRequest) {
		//1，校验请求对象是否为null，是null抛出异常，非null继续执行
		checkSearchRequest(searchRequest);

		//3，获取过滤参数
		Map<String, String> filter = searchRequest.getFilter();

		BoolQueryBuilder boolQueryBuilder = getBoolQueryBuilder(searchRequest, "filter");


		//2，获取搜索关键字，并根据关键字获取本地查询建造者对象
		String key = searchRequest.getKey();
		NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

		//3，添加分页条件，我们重点在于获取聚合结果，这里象征性查询一条记录
		queryBuilder.withPageable(PageRequest.of(0, 1));
		queryBuilder.withQuery(boolQueryBuilder);//添加布尔查询条件

		//4，创建一个空的Map集合，用于接收查询到的结果，最终返回
		LinkedHashMap<String, List<?>> lhmap = new LinkedHashMap<>();
		/**
		 * terms:表示分桶的类型，使用词条分桶，即词条完全匹配时才会进行分桶
		 * terms(brandAgg)：方法内的参数表示给该分桶起一个名字
		 * field("brandId")：表示根据哪个字段进行分桶
		 */
		//5，添加聚合条件，
		String brandAggName = "brandAgg";
		String categoryAggName = "categoryAgg";
		//5.1，添加根据brandId进行聚合的条件
		queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
		//5.2，添加根据categoryId进行聚合的条件
		queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("categoryId"));

		//6，发起查询，获取结果
		AggregatedPage<Good> result = esTemplate.queryForPage(queryBuilder.build(), Good.class);

		//7，解析结果
		Aggregations aggregations = result.getAggregations();
		//7.1，处理分类聚合结果
		LongTerms categoryTerm = aggregations.get(categoryAggName);
		//7.2，获取分类聚合结果中的分桶集合，桶中的key就是cid
		List<LongTerms.Bucket> categoryBuckets = categoryTerm.getBuckets();
		//7.3，将所有桶中的key转为Long类型，获取cid的list集合
		List<Long> cids = categoryBuckets.stream()
				.map(LongTerms.Bucket::getKeyAsNumber)
				.map(Number::longValue)
				.collect(Collectors.toList());

		//7.4当聚合得到到分类不为空时，查询具体分类详情，然后放到map集合中
		if (!CollectionUtils.isEmpty(cids) && cids.size() == 1) {
			List<CategoryDto> categoryDtos = itemClient.queryCategoriesByCids(cids);
			lhmap.put("分类", categoryDtos);
			//索引库中可过滤参数的查询
			queryFilterSpecParam(searchRequest, lhmap, cids.get(0));
		}

		//2，获取品牌聚合结果
		LongTerms brandTerm = aggregations.get(brandAggName);
		List<Long> bids = brandTerm.getBuckets().stream()
				.map(LongTerms.Bucket::getKeyAsNumber)
				.map(Number::longValue)
				.collect(Collectors.toList());
		if (!CollectionUtils.isEmpty(bids)) {
			List<BrandDto> brandDtoList = itemClient.queryBrandsByBrandIds(bids);
			lhmap.put("品牌", brandDtoList);
		}
		//删除用户已经选择了的过滤选项
		// Map<String, String> filter = searchRequest.getFilter();
		if(!StringUtils.isEmpty(filter)){
			Set<Map.Entry<String, String>> entries = filter.entrySet();
			for (Map.Entry<String, String> entry : entries) {
				lhmap.remove(entry.getKey());
			}
		}

		//返回封装好的map集合
		return lhmap;
	}

	/**
	 * 根据第三级分类id查询上两级的分类
	 * @param cid3 第三级分类id
	 * @return 封装了三级分类的dto对象
	 */
	@Override
	public List<CategoryDto> queryCategoriesByCid3(Long cid3) {
		return itemClient.queryCategoriesByCid3(cid3);
	}

	/**
	 * 根据分类id和过滤参数查询剩余可供用户选择的过滤参数
	 *
	 * @param searchRequest   请求对象
	 * @param lhmap 封装索引库中可用来过滤的参数
	 */
	private void queryFilterSpecParam(SearchRequest searchRequest, LinkedHashMap<String, List<?>> lhmap, Long cid) {
		//当用户输入的搜索条件查询到的结果只有一个品类时，将该品类下的所有可过滤的参数查询出来返回前端，供用户过滤
		List<SpecParamDTO> specParamDTOList = itemClient.queryParamsByGroupIdOrCategoryId(cid, true);
		//根据规格参数的名称进行聚合
		NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();


		queryBuilder.withQuery(getBoolQueryBuilder(searchRequest,"filter"));
		//遍历添加所有的可搜索规格参数的聚合
		specParamDTOList.forEach(specParamDTO -> {
			//根据可搜索的规格参数的名称对索引库中的指定分类进行聚合
			String specparamName = specParamDTO.getName();
			StringBuffer sb = new StringBuffer().append("specs.").append(specparamName).append(".keyword");

			//添加聚合条件，聚合名称为规格参数名称，聚合条件为规格参数的名称
			queryBuilder.addAggregation(AggregationBuilders.terms(specparamName).field(sb.toString()));
			//添加分页条件
			queryBuilder.withPageable(PageRequest.of(0, 1));//查询一条数据，主要想得到聚合结果，非搜索结果
		});
		//进行聚合搜索，获取聚合结果
		Aggregations aggregations = esTemplate.queryForPage(queryBuilder.build(), Good.class).getAggregations();

		//遍历获取每个聚合，然后获取每个聚合中的分桶，将分桶中的数据转换成一个list集合，
		specParamDTOList.forEach(specParamDTO -> {
			String name = specParamDTO.getName();
			StringTerms stringTerms = aggregations.get(name);//根据聚合名称获取指定聚合
			//根据聚合获取分桶，然后将分桶中的key转换成一个list集合，集合中放的是可过滤规格参数
			List<String> searchingParams = stringTerms.getBuckets().stream().map(StringTerms.Bucket::getKeyAsString).collect(Collectors.toList());

			//将分桶名称
			lhmap.put(name,searchingParams);
		});
	}


	private void checkSearchRequest(SearchRequest searchRequest){
		if(null == searchRequest){throw new LyException(ItemExceptionEnum.QUERY_PARAM_ERROR);}
	}

}
